import type { ParticleNetwork, SolanaWallet } from '@particle-network/solana-wallet';
import type { WalletName } from '@solana/wallet-adapter-base';
import {
    BaseMessageSignerWalletAdapter,
    WalletAccountError,
    WalletConfigError,
    WalletDisconnectionError,
    WalletLoadError,
    WalletNotConnectedError,
    WalletNotReadyError,
    WalletPublicKeyError,
    WalletReadyState,
    WalletSignMessageError,
    WalletSignTransactionError,
} from '@solana/wallet-adapter-base';
import type { Transaction } from '@solana/web3.js';
import { PublicKey } from '@solana/web3.js';

export interface ParticleAdapterConfig {
    config?: ConstructorParameters<typeof ParticleNetwork>[0];
    login?: Parameters<SolanaWallet['connect']>[0];
}

export const ParticleName = 'Particle' as WalletName<'Particle'>;

export class ParticleAdapter extends BaseMessageSignerWalletAdapter {
    name = ParticleName;
    url = 'https://particle.network';
    icon =
        '';
    readonly supportedTransactionVersions = null;

    private _connecting: boolean;
    private _wallet: SolanaWallet | null;
    private _publicKey: PublicKey | null;
    private _config: ParticleAdapterConfig;
    private _readyState: WalletReadyState =
        typeof window === 'undefined' ? WalletReadyState.Unsupported : WalletReadyState.Loadable;

    private _particle: ParticleNetwork | null = null;

    constructor(config: ParticleAdapterConfig = {}) {
        super();
        this._connecting = false;
        this._publicKey = null;
        this._wallet = null;

        this._config = {
            config: {
                projectId: '',
                clientKey: '',
                appId: '',
                ...config.config,
                chainId: config.config?.chainId ?? 101,
                chainName: config.config?.chainName ?? 'solana',
            },
            login: config.login,
        };
    }

    get particle(): ParticleNetwork | null {
        return this._particle;
    }

    get publicKey() {
        return this._publicKey;
    }

    get connecting() {
        return this._connecting;
    }

    get readyState() {
        return this._readyState;
    }

    async connect(): Promise<void> {
        try {
            if (this.connected || this.connecting) return;
            if (this._readyState !== WalletReadyState.Loadable) throw new WalletNotReadyError();

            this._connecting = true;

            let ParticleClass: typeof ParticleNetwork;
            let WalletClass: typeof SolanaWallet;
            try {
                ({ ParticleNetwork: ParticleClass, SolanaWallet: WalletClass } = await import(
                    '@particle-network/solana-wallet'
                ));
            } catch (error: any) {
                throw new WalletLoadError(error?.message, error);
            }

            let particle: ParticleNetwork;
            try {
                particle = new ParticleClass(this._config.config);
                if (!particle.auth.isLogin()) {
                    await particle.auth.login(this._config.login);
                }
            } catch (error: any) {
                throw new WalletConfigError(error?.message, error);
            }

            let wallet: SolanaWallet;
            try {
                wallet = new WalletClass(particle.auth);
            } catch (error: any) {
                throw new WalletConfigError(error?.message, error);
            }

            const account = wallet.publicKey;
            if (!account) throw new WalletAccountError();

            let publicKey: PublicKey;
            try {
                publicKey = new PublicKey(account.toBytes());
            } catch (error: any) {
                throw new WalletPublicKeyError(error?.message, error);
            }

            this._particle = particle;
            this._wallet = wallet;
            this._publicKey = publicKey;

            this.emit('connect', publicKey);
        } catch (error: any) {
            this.emit('error', error);
            throw error;
        } finally {
            this._connecting = false;
        }
    }

    async disconnect(): Promise<void> {
        const wallet = this._wallet;
        if (wallet) {
            this._wallet = null;
            this._publicKey = null;

            try {
                await wallet.disconnect();
            } catch (error: any) {
                this.emit('error', new WalletDisconnectionError(error?.message, error));
            }
        }

        this.emit('disconnect');
    }

    async signTransaction<T extends Transaction>(transaction: T): Promise<T> {
        try {
            const wallet = this._wallet;
            if (!wallet) throw new WalletNotConnectedError();

            try {
                return ((await wallet.signTransaction(transaction)) as T) || transaction;
            } catch (error: any) {
                throw new WalletSignTransactionError(error?.message, error);
            }
        } catch (error: any) {
            this.emit('error', error);
            throw error;
        }
    }

    async signAllTransactions<T extends Transaction>(transactions: T[]): Promise<T[]> {
        try {
            const wallet = this._wallet;
            if (!wallet) throw new WalletNotConnectedError();

            try {
                return ((await wallet.signAllTransactions(transactions)) as T[]) || transactions;
            } catch (error: any) {
                throw new WalletSignTransactionError(error?.message, error);
            }
        } catch (error: any) {
            this.emit('error', error);
            throw error;
        }
    }

    async signMessage(message: Uint8Array): Promise<Uint8Array> {
        try {
            const wallet = this._wallet;
            if (!wallet) throw new WalletNotConnectedError();

            try {
                return await wallet.signMessage(message);
            } catch (error: any) {
                throw new WalletSignMessageError(error?.message, error);
            }
        } catch (error: any) {
            this.emit('error', error);
            throw error;
        }
    }
}
